﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using FFmpegVideoClip.Configuration;
using FFmpegVideoClip.Lib;
using FFmpegVideoClip.Lib.AdvancedFragmentProcessor;
using FFmpegVideoClip.Mode;


namespace FFmpegVideoClip.Lib.ClipGroups
{
    internal class GroupMerge
    {
        private record class GroupMergeData(Clip Clip, AdvancedParametersSchemeModule Module);
        private static readonly AdvancedParametersSchemeModule ModuleTemplate;
        public static string TempCheckPath;

#pragma warning disable CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
        static GroupMerge()
        {
            ModuleTemplate = new AdvancedParametersSchemeModule()
            {
                ProcessorName = Config.CoreProcessor,
                SchemeName = "内部音视频文件合并",
                Command = Tools.FFmpegMergeArguments(ParametersSign.InputSignName, ParametersSign.OutputSignName)
            };
            ModuleTemplate.GenerateArgumentsList();
        }
#pragma warning restore CS8618 // 在退出构造函数时，不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。
        /********************************************************************************************************************/
        public object SynchronizationObject = new();
        public object PreProcessSynchronizationObject = new();
        private int AfterprocessingTaskFlowID = 20000;
        private List<Fragment> MergeAllClipToFragments = new();
        private List<GropuMergeTaskFlow> GroupList = new();
        private List<GropuMergeTaskFlow> ReadyGroupList = new();
        
        private ConcurrentQueue<GropuMergeTaskFlow> TaskQueue = new();
        private List<GropuMergeTaskFlow> ErrorTask = new();
        public double TotalSeconds => GroupList.Sum(p => p.successAndProgress.TotalSeconds);
        public double TotalCompletedSeconds => GroupList.Sum(p => p.successAndProgress.CompletedTimingSeconds);
        private bool IsExecute => ReadyGroupList.Count > 0;

        private class SchemeModuleArgumentsEqualityComparer : IEqualityComparer<AdvancedParametersSchemeModule>
        {
            public bool Equals(AdvancedParametersSchemeModule? x, AdvancedParametersSchemeModule? y)
            {
                if (x == null || y == null)
                    return false;
                return x.SchemeModuleEquals(y);
            }
            public int GetHashCode([DisallowNull] AdvancedParametersSchemeModule obj) { throw new NotImplementedException(); }
        }
        public static bool ClipGooupModuleArgumentsCheck()
        {
            StringBuilder sb = new StringBuilder();
            SchemeModuleArgumentsEqualityComparer Comparer = new SchemeModuleArgumentsEqualityComparer();
            foreach (Clip item in ClipManage.ClipList)
            {
                if (item.Group is null || item.Group.Clips.Count < 2)
                    continue;

                foreach (Clip ChildItem in item.Group.Clips)
                {
                    //跳过自身
                    if (ChildItem == item)
                        continue;
                    if (!item.Flows.SequenceEqual(ChildItem.Flows, Comparer) ||
                        (item.AfterprocessingFlows.Count > 0 && item.AfterprocessingFlows.Count == ChildItem.AfterprocessingFlows.Count
                        && !item.AfterprocessingFlows.SequenceEqual(ChildItem.AfterprocessingFlows, Comparer)))
                    { sb.AppendLine($"组:[{item.Name}]==>子剪辑[{ChildItem.Name}]"); }
                }
            }

            if (sb.Length > 2 && MessageBoxEx.CustomButtonTextMessageBoxShow(Application.Current.MainWindow, "合并组的处理过程或参数存在不一致，这可能导致媒体文件错误\r\n确认要继续合并么?", "组合并警告", MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.Cancel, null, "继续合并", "取消") == MessageBoxResult.Cancel)
            {
                sb.Insert(0, "合并组的处理过程或参数存在不一致，这可能导致媒体文件错误，请确保以下对应的剪辑处理过程及参数一致\r\n{\r\n");
                sb.AppendLine("}");
                LogManage.WriteLineNewInfo(sb.ToString(), true);
                return false;
            }
            return true;
        }
        public void Preparation()
        {
            TempCheckPath = Path.Combine(Config.CacheDirectory, $"{DateTime.Now.ToString("ggyyyy-MM-dd(dddd)tthh.mm.ss")}_合并临时缓存");
            Preparation(ClipsGroupManage.Instance.Groups);
        }
        public void Preparation(IEnumerable<ClipsGroup> clipsGroups)
        {
            if (string.IsNullOrEmpty(TempCheckPath))
                TempCheckPath = Path.Combine(Config.CacheDirectory, $"{DateTime.Now.ToString("ggyyyy-MM-dd(dddd)tthh.mm.ss")}_合并临时缓存");
            IEnumerable<ClipsGroup> Groups = clipsGroups.Where(p => p.Clips.Count > 1);
            Fragment TempFragment;
            GropuMergeTaskFlow flow;
            //Clip 转 Fragment，TempFragment.clip下挂载的是转换前的源Clip
            Groups.SelectMany(p => p.Clips).WhereKeyIntersection(MergeAllClipToFragments, p => p.IndexUI, p => p.ID).Distinct().ForEach(p =>
            {
                TempFragment = new Fragment();
                TempFragment.StartTimeValue = TextBoxTextTimeInputChangedLimit.DefaultHistorical;
                TempFragment.EndTimeValue = Tools.GetTimeSpanToString(p.TimeDuration);
                TempFragment.InputPath = string.Empty; ;
                TempFragment.Name = p.Name;
                TempFragment.ID = p.IndexUI;
                TempFragment.clip = p;
                MergeAllClipToFragments.Add(TempFragment);
            });
            // 创建用于组的Clip对象，每个Clip相当于组，每个Fragment相当于Clip
            Groups.ForEach(p =>
            {
                Clip clip = new Clip();
                clip.Name = $"{p.MainClip.Name}_组合并";
                clip.OutDirectory = p.MainClip.OutDirectory;
                clip.Group = p;
                //此处add因为有内部操作所以不能用，因此直接赋值到 Fragments 属性上
                //在Clip里挂载的Fragment是公用的，而不是进自身的，因为组存在交叉，使用公用对象好处理文件和路径
                clip.Fragments = new(MergeAllClipToFragments.WhereKeyIntersection(p.Clips, p => p.ID, p => p.IndexUI));
                AdvancedParametersSchemeModule module = ModuleTemplate.DeepCopy();
                flow = new GropuMergeTaskFlow(clip, module, AfterprocessingTaskFlowID++, p.MainClip.IndexUI);
                lock (SynchronizationObject)
                {
                    GroupList.Add(flow);
                    ReadyGroupList.Add(flow);
                }
            });
            if (Config.IsEnableWriteLog)
                foreach (GropuMergeTaskFlow ReadyGroup in ReadyGroupList)
                    LogManage.WriteLineNewInfo(GetInfo(ReadyGroup));
        }

        private void CheckGroupClipFileSuccessAnyGet()
        {
            lock (SynchronizationObject)
            {
                ReadyGroupList.Where(p => !p.ThisClip!.Fragments.Any(cf =>
                cf.clip!.CompletionStatus != CompletionStatusEnum.Success
                || !(string.IsNullOrWhiteSpace(cf.InputPath) ? File.Exists(cf.clip.FileWritePath) : File.Exists(cf.InputPath))))
                    .ToArray().ForEach(task =>
                    {
                        LogManage.WriteLineNewInfo($"[{task.ThisClip!.Name}] 添加到队列");
                        TaskQueue.Enqueue(task);
                        ReadyGroupList.Remove(task);
                    });
            }
        }
        private void PreProcess(GropuMergeTaskFlow GropuFlow)
        {
            if (!Directory.Exists(TempCheckPath))
                Directory.CreateDirectory(TempCheckPath);
            lock (PreProcessSynchronizationObject)
            {
                //Fragments.InputPath 未设置 执行转移文件，已设置过的跳过
                //因为Fragments是公有对象，所以设置后在所有组剪辑里，都会跟着更改
                GropuFlow.ThisClip!.Fragments.Where(p => string.IsNullOrEmpty(p.InputPath)).AsParallel()
                    .WithDegreeOfParallelism(Config.MaxThread).WithMergeOptions(ParallelMergeOptions.NotBuffered)
                    .WithExecutionMode(ParallelExecutionMode.ForceParallelism)
                    .WithCancellation(Tools.CurrentCancelToken!.Token)
                    .ForAll(GroupElement =>
                    {
                        string NewPaht = Path.Combine(TempCheckPath, Path.GetFileName(GroupElement.clip!.FileWritePath));
                        if (File.Exists(GroupElement.clip!.FileWritePath))
                        {
                            Tools.CheckDuplicateNameAndLegal(ref NewPaht, true);
                            File.Move(GroupElement.clip!.FileWritePath, NewPaht);
                            GroupElement.InputPath = GroupElement.clip.GruopMageCachePaht = NewPaht;
                            return;
                        }
                        //GroupElement.InputPath=null 代表该元素未设置，原始FileWritePath中应该存在文件，但是，原始文件不存在，GroupElement.InputPath又未设置，只能是原始文件缺失所致
                        if (string.IsNullOrEmpty(GroupElement.InputPath))
                            throw new FileNotFoundException($"处理[{GropuFlow.Name}]组文件转移到临时目录时，缺少文件[{GroupElement.clip.FileWritePath}]");
                        // 原始路径文件不存在，并且GroupElement.InputPath有已经设置过，但是目标路径下有没文件，这就是个未知错误！
                        if (!File.Exists(GroupElement.InputPath))
                            throw new FileNotFoundException($"处理[{GropuFlow.Name}]组文件转移到临时目录时，发生未知错误，输入路径[{GroupElement.InputPath}]文件缺失，原始路径[{GroupElement.clip.FileWritePath}]不存在");
                    });
            }
        }
        public void CheckGroupClipAndExecute()
        {
            if (!IsExecute)
                return;
            if (Tools.IsCancellationRequested)
                return;
            CheckGroupClipFileSuccessAnyGet();
            while (TaskQueue.TryDequeue(out GropuMergeTaskFlow? task))
            {
                if (Tools.IsCancellationRequested)
                    return;
                try
                {
                    PreProcess(task);
                    if (Tools.IsCancellationRequested)
                        return;
                    task.Init();
                    task.Execute();
                }
                catch (Exception ex)
                {
                    lock (((ICollection)ErrorTask).SyncRoot)
                        ErrorTask.Add(task);
                    throw new GroupMergeException($"合并异常：{task.ThisClip!.Name}", ex);
                }
            }
        }
        
        public void MoveSingleFile()
        {
            CheckError();
            ClipManage.ClipList.Where(p => p.IsExistsInGroup && p.EnableSingleOut).AsParallel()
            .WithDegreeOfParallelism(Config.MaxThread).WithMergeOptions(ParallelMergeOptions.NotBuffered).WithExecutionMode(ParallelExecutionMode.ForceParallelism).WithCancellation(Tools.CurrentCancelToken!.Token)
            .ForAll(clip =>
            {
                if (Tools.IsCancellationRequested)
                    return;
                string NewFileFullName = Path.Combine(clip.OutDirectory, $"{clip.Name}{Path.GetExtension(clip.FileWritePath)}");
                Tools.CheckDuplicateNameAndLegal(ref NewFileFullName, true);
                File.Move(clip.GruopMageCachePaht, NewFileFullName);
            });
            if (Config.AutoClearCache && !string.IsNullOrWhiteSpace(TempCheckPath) && Directory.Exists(TempCheckPath))
                Directory.Delete(TempCheckPath, true);
        }
    
        private void CheckError()
        {
            StringBuilder stringBuilder = new StringBuilder();

            foreach (var item in ReadyGroupList)
                stringBuilder.AppendLine($"未执行的组合并:\r\n{GetInfo(item)}");
            foreach (var item in ErrorTask)
                stringBuilder.AppendLine($"执行的异常的组合并:\r\n{GetInfo(item)}");
            if (stringBuilder.Length > 2)
                throw new GroupMergeException($"合并失败的组:\r\n[{stringBuilder.ToString()}\r\n]");
        }
        public void Cancel(Clip clip)
        {
            GropuMergeTaskFlow? TaskFlow; string TempName;
            Fragment? fragment = MergeAllClipToFragments.FirstOrDefault(p => p.ID == clip.IndexUI);
            if (fragment != null)
                MergeAllClipToFragments.Remove(fragment);
            ClipsGroupManage.GetAssociatedGroup(clip, out ClipsGroup[] groups);
            foreach (ClipsGroup item in groups)
            {
                TaskFlow = GroupList.FirstOrDefault(p => p.ThisClip?.Group?.GroupID == clip.Group?.GroupID);
                if (TaskFlow == null)
                    continue;
                GroupList.Remove(TaskFlow);
                ReadyGroupList.Remove(TaskFlow);
                TempName = TaskFlow.ThisClip!.Name;
                TaskFlow.Dispose();
                Application.Current.Dispatcher.BeginInvoke(() => ProgressStatistics.RemoveClip(TempName));
            }
        }
        private static string GetInfo(GropuMergeTaskFlow flow)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("[");
            sb.AppendLine($"    GroupName:{flow.ThisClip?.Name}");
            sb.AppendLine($"    GroupFileWritePath:{flow.ThisClip?.FileWritePath}:[{File.Exists(flow.ThisClip?.FileWritePath)}]");
            foreach (Fragment item in flow.ThisClip?.Fragments.ToArray() ?? [])
            {
                sb.AppendLine("    [");
                sb.AppendLine($"      ElementName:{item.Name}");
                sb.AppendLine($"      ElementInputPath:{item.InputPath}:[{File.Exists(item.InputPath)}]");
                sb.AppendLine($"      ElementClipFileWritePath:{item.clip?.FileWritePath}:[{File.Exists(item.clip?.FileWritePath)}]");
                sb.AppendLine("    ]");
            }
            sb.AppendLine("]");
            return sb.ToString();
        }

        [Serializable]
        public class GroupMergeException : Exception
        {
            public GroupMergeException() { }
            public GroupMergeException(string message) : base(message) { }
            public GroupMergeException(string message, Exception inner) : base(message, inner) { }
            [Obsolete] protected GroupMergeException(
              System.Runtime.Serialization.SerializationInfo info,
              System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
        }
    }
}
